/*
 CMDI PlanPlat项目 @author yanghongdong
*/
;
(function (global, factory) {
  'use strict'

  typeof exports === 'object' && typeof module !== 'undefined' ?
    factory(exports) :
    typeof define === 'function' && define.amd ?
    define(['exports'], factory) :
    factory((global.N = {}))
})(this, function (exports) {
  'use strict'

  var version = '1.0.0'

  var gApi = {}
  var defaultAction

  var tianmapProvider = {
    normal: {
      // 矢量地图和中文注记
      map: 'http://{s}.tianditu.com/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles',
      annotion: 'http://t0.tianditu.com/cva_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cva&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default.jpg'
    },
    satellite: {
      // 影像地图和中文注记
      map: 'http://{s}.tianditu.com/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles',
      annotion: 'http://t0.tianditu.com/cia_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default.jpg'
    }
  }

  /*
   * @namespace Constant
   *
   * 常量
   */
  var Constant = (Object.freeze || Object)({
    tianmapProvider: tianmapProvider
  })

  /*
   * @namespace Util
   *
   * Various utility functions, used by Cmdi internally.
   */

  var freeze = Object.freeze
  Object.freeze = function (obj) {
    return obj
  }

  // @function extend(dest: Object, src?: Object): Object
  // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
  function extend(dest) {
    var i, j, len, src

    for (j = 1, len = arguments.length; j < len; j++) {
      src = arguments[j]
      for (i in src) {
        dest[i] = src[i]
      }
    }
    return dest
  }

  // @function create(proto: Object, properties?: Object): Object
  // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
  var create =
    Object.create ||
    (function () {
      function F() {}
      return function (proto) {
        F.prototype = proto
        return new F()
      }
    })()

  // @function bind(fn: Function, …): Function
  // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
  // Has a `L.bind()` shortcut.
  function bind(fn, obj) {
    var slice = Array.prototype.slice

    if (fn.bind) {
      return fn.bind.apply(fn, slice.call(arguments, 1))
    }

    var args = slice.call(arguments, 2)

    return function () {
      return fn.apply(
        obj,
        args.length ? args.concat(slice.call(arguments)) : arguments
      )
    }
  }

  // @function trim(str: String): String
  // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
  function trim(str) {
    return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '')
  }

  // @function rtrim(str: String, mask: String): String
  function rtrim(str, mask) {
    if (str && mask) {
      return str.replace(new RegExp('\\' + mask + '+$', 'g'), '')
    }

    return str.replace(/\s+$/g, '')
  }

  // @function splitWords(str: String): String[]
  // Trims and splits the string on whitespace and returns the array of parts.
  function splitWords(str) {
    return trim(str).split(/\s+/)
  }

  // @function isArray(obj): Boolean
  // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
  var isArray =
    Array.isArray ||
    function (obj) {
      return Object.prototype.toString.call(obj) === '[object Array]'
    }

  var isObject =
    Object.isObject ||
    function (obj) {
      return Object.prototype.toString.call(obj) === '[object Object]'
    }

  // @function indexOf(array: Array, el: Object): Number
  // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
  function indexOf(array, el) {
    for (var i = 0; i < array.length; i++) {
      if (array[i] === el) {
        return i
      }
    }
    return -1
  }

  function isRepeat(arr) {
    var hash = {}

    for (var i = 0, item;
      (item = arr[i]); i++) {
      if (hash[item]) {
        return true
      }

      hash[item] = true
    }

    return false
  }

  function getHeight(height, fileName) {
    var sHeight = -(-height)

    if (typeof sHeight !== 'number' && fileName) {}

    return sHeight
  }

  function getXHRReadyStateInfo(code) {
    var sCode = parseInt(code, 10)

    if (typeof sCode !== 'number' || $.inArray(sCode, [0, 1, 2, 3, 4]) === -1) {
      return false
    }

    var info = {
      0: '未初始化 -- 尚未调用.open()方法',
      1: '启动 -- 已经调用.open()方法，但尚未调用.send()方法',
      2: '发送 -- 已经调用.send()方法，但尚未接收到响应',
      3: '接收 -- 已经接收到部分响应数据',
      4: '接收 -- 已经接收到部分响应数据'
    }

    return info[code]
  }

  function printAjaxError(message) {
    // 请求失败
    var msg1 = '请求' + message.url + '时出错',
      msg2 = '响应状态码：' + message.status,
      msg3 = '响应状态描述：' + message.statusText,
      msg4 =
      'readyState：' +
      message.readyState +
      '，' +
      getXHRReadyStateInfo(message.readyState)

    console.log(msg1, msg2, msg3, msg4)
  }

  // 返回的数据不安全
  function usAjax(type, ajaxOpt, callbacks) {
    if (ajax.cache) {
      if (!gApi[type]) {
        gApi[type] = $.ajax({
          url: ajaxOpt.url,
          method: ajaxOpt.method,
          dataType: ajaxOpt.dataType,
          data: ajaxOpt.data
        })
      }
    } else {
      gApi[type] = $.ajax({
        url: ajaxOpt.url,
        method: ajaxOpt.method,
        dataType: ajaxOpt.dataType,
        data: ajaxOpt.data
      })
    }

    gApi[type]
      .done(function (data) {
        if (!isObject(data)) {
          console.error('后台未返回合法数据: ', data)
        } else {
          var length = callbacks.length

          for (var i = 0; i < length; i++) {
            callbacks[i](data)
          }
        }
      })
      .fail(function (jqxhr) {
        printAjaxError({
          status: jqxhr.status,
          statusText: jqxhr.statusText,
          readyState: jqxhr.readyState,
          url: this.url // 注意这里的this指向的是ajax对象，其中包含了请求参数
        })
      })
  }

  // POST方法：jeeplus框架返回的 {success: true, errorCode: "-1", msg: "保存成功"}格式的数据
  function postLoadingAjax(type, ajaxOpt, successCalls, failCalls) {
    var loadingIndex = ajaxOpt.loadingIndex

    gApi[type] = $.ajax({
      url: ajaxOpt.url,
      method: 'POST',
      dataType: 'json',
      data: ajaxOpt.data,
      contentType: ajaxOpt.contentType ||
        'application/x-www-form-urlencoded; charset=UTF-8'
    })

    gApi[type]
      .done(function (data) {
        jp.close(loadingIndex)

        if (!isObject(data)) {
          jp.error('操作失败')
          throw new Error('后台未返回合法json数据')
          console.error('后台未返回合法数据: ', data)
        }

        if (data.hasOwnProperty('success')) {
          var length = successCalls.length

          for (var i = 0; i < length; i++) {
            successCalls[i](data)
          }
        } else {
          jp.error('操作失败')
          throw new Error('后台未返回合法json数据')
          console.error('后台未返回合法数据: ', data)
        }
      })
      .fail(function (jqxhr) {
        jp.close(loadingIndex)

        var length = failCalls.length

        for (var i = 0; i < length; i++) {
          failCalls[i](jqxhr)
        }

        printAjaxError({
          status: jqxhr.status,
          statusText: jqxhr.statusText,
          readyState: jqxhr.readyState,
          url: this.url // 注意这里的this指向的是ajax对象，其中包含了请求参数
        })
      })
  }

  // POST方法：jeeplus框架返回的 {success: true, errorCode: "-1", msg: "保存成功"}格式的数据
  function postBsAjax(type, ajaxOpt, successCalls, failCalls) {
    gApi[type] = $.ajax({
      url: ajaxOpt.url,
      method: 'POST',
      dataType: 'json',
      data: ajaxOpt.data,
      contentType: ajaxOpt.contentType ||
        'application/x-www-form-urlencoded; charset=UTF-8'
    })

    gApi[type]
      .done(function (data) {
        if (!isObject(data)) {
          throw new Error('后台未返回合法json数据')
          console.error('后台未返回合法数据: ', data)
        }

        if (data.hasOwnProperty('success')) {
          var length = successCalls.length

          for (var i = 0; i < length; i++) {
            successCalls[i](data)
          }
        } else {
          throw new Error('后台未返回合法json数据')
          console.error('后台未返回合法数据: ', data)
        }
      })
      .fail(function (jqxhr) {
        var length = failCalls.length

        for (var i = 0; i < length; i++) {
          failCalls[i](jqxhr)
        }

        printAjaxError({
          status: jqxhr.status,
          statusText: jqxhr.statusText,
          readyState: jqxhr.readyState,
          url: this.url // 注意这里的this指向的是ajax对象，其中包含了请求参数
        })
      })
  }

  // GET方法：jeeplus框架返回的 {success: true, errorCode: "-1", body: {...}}格式的数据
  function bsAjax(type, ajaxOpt, successCalls, failCalls) {
    if (ajaxOpt.cache) {
      if (!gApi[type]) {
        gApi[type] = $.ajax({
          url: ajaxOpt.url,
          method: ajaxOpt.method || 'GET',
          dataType: ajaxOpt.dataType || 'json',
          data: ajaxOpt.data
        })
      }
    } else {
      gApi[type] = $.ajax({
        url: ajaxOpt.url,
        method: ajaxOpt.method || 'GET',
        dataType: ajaxOpt.dataType || 'json',
        data: ajaxOpt.data
      })
    }

    gApi[type]
      .done(function (data) {
        if (data.success === true && data.body) {
          var usData = data.body.data,
            length = successCalls.length

          if (!isObject(usData) && !isArray(usData)) {
            // console.error('后台未返回合法数据: ', data);

            if (failCalls) {
              for (var i = 0, fun;
                (fun = failCalls[i]); i++) {
                fun(data)
              }
            }
            console.log('后台未返回合法数据: ', data)
          } else {
            for (var i = 0; i < length; i++) {
              successCalls[i](usData)
            }
          }
        } else {
          if (failCalls) {
            for (var i = 0, fun;
              (fun = failCalls[i]); i++) {
              fun(data)
            }
          }
          console.log('后台未返回合法数据: ', data)
        }
      })
      .fail(function (jqxhr) {
        if (failCalls) {
          for (var i = 0, fun;
            (fun = failCalls[i]); i++) {
            fun()
          }
        }

        printAjaxError({
          status: jqxhr.status,
          statusText: jqxhr.statusText,
          readyState: jqxhr.readyState,
          url: this.url // 注意这里的this指向的是ajax对象，其中包含了请求参数
        })
      })
  }

  // GET方法：jeeplus框架返回的 {success: true, errorCode: "-1", msg: "保存成功"}格式的数据
  function getLoadingAjax(type, ajaxOpt, successCalls, failCalls) {
    var loadingIndex = ajaxOpt.loadingIndex

    gApi[type] = $.ajax({
      url: ajaxOpt.url,
      method: 'GET',
      dataType: 'json',
      data: ajaxOpt.data,
      cache: false
    })

    gApi[type]
      .done(function (data) {
        jp.close(loadingIndex)

        if (!isObject(data)) {
          jp.error('操作失败')
          throw new Error('后台未返回合法json数据')
          console.error('后台未返回合法数据: ', data)
        }

        if (data.hasOwnProperty('success')) {
          var length = successCalls.length

          for (var i = 0; i < length; i++) {
            successCalls[i](data)
          }
        } else {
          jp.error('操作失败')
          throw new Error('后台未返回合法json数据')
          console.error('后台未返回合法数据: ', data)
        }
      })
      .fail(function (jqxhr) {
        jp.close(loadingIndex)

        var length = failCalls.length

        for (var i = 0; i < length; i++) {
          failCalls[i](jqxhr)
        }

        printAjaxError({
          status: jqxhr.status,
          statusText: jqxhr.statusText,
          readyState: jqxhr.readyState,
          url: this.url // 注意这里的this指向的是ajax对象，其中包含了请求参数
        })
      })
  }

  function ajax(type, ajaxOpt, callbacks) {
    if (ajax.cache) {
      if (!gApi[type]) {
        gApi[type] = $.ajax({
          url: ajaxOpt.url,
          method: ajaxOpt.method || 'GET',
          dataType: ajaxOpt.dataType || 'json',
          data: ajaxOpt.data
        })
      }
    } else {
      gApi[type] = $.ajax({
        url: ajaxOpt.url,
        method: ajaxOpt.method || 'GET',
        dataType: ajaxOpt.dataType || 'json',
        data: ajaxOpt.data
      })
    }

    gApi[type]
      .done(function (data) {
        if (!isObject(data) && !isArray(data)) {
          console.error('后台未返回合法数据: ', data)
        } else {
          var length = callbacks.length

          for (var i = 0; i < length; i++) {
            callbacks[i](data)
          }
        }
      })
      .fail(function (jqxhr) {
        printAjaxError({
          status: jqxhr.status,
          statusText: jqxhr.statusText,
          readyState: jqxhr.readyState,
          url: this.url // 注意这里的this指向的是ajax对象，其中包含了请求参数
        })
      })
  }

  function getSeriesAndXaxisData(data) {
    var len = data.length,
      seriesData = [],
      xAxisData = []

    for (var i = 0; i < len; i++) {
      xAxisData.push(data[i].name)
      seriesData.push(data[i].value)
    }

    return {
      seriesData: seriesData,
      xAxisData: xAxisData
    }
  }

  function ready(fn) {
    if (document.readyState != 'loading') {
      fn()
    } else if (document.addEventListener) {
      document.addEventListener('DOMContentLoaded', fn)
    } else {
      document.attachEvent('onreadystatechange', function () {
        if (document.readyState != 'loading') {
          fn()
        }
      })
    }
  }

  var Util = (Object.freeze || Object)({
    freeze: freeze,
    extend: extend,
    create: create,
    bind: bind,
    trim: trim,
    rtrim: rtrim,
    splitWords: splitWords,
    isArray: isArray,
    isObject: isObject,
    indexOf: indexOf,
    isRepeat: isRepeat,
    getHeight: getHeight,
    getXHRReadyStateInfo: getXHRReadyStateInfo,
    printAjaxError: printAjaxError,
    ajax: ajax,
    usAjax: usAjax,
    bsAjax: bsAjax,
    postBsAjax: postBsAjax,
    postLoadingAjax: postLoadingAjax,
    getLoadingAjax: getLoadingAjax,
    getSeriesAndXaxisData: getSeriesAndXaxisData
  })

  function getOneDimArray(twoDimArr, index) {
    var oneDimArr = []

    for (var i = 0, item;
      (item = twoDimArr[i]); i++) {
      oneDimArr.push(item[index])
    }

    return oneDimArr
  }

  function getHCorrectList(list) {
    var len = list.length
    var listCorrect = []

    for (var i = 0; i < len; i++) {
      var item = list[i]

      listCorrect.push({
        horizontalGain: item.horizontalGain,
        horizontalAngel: item.horizontalAngel
      })
    }

    return listCorrect
  }

  function getVCorrectList(list) {
    var len = list.length
    var listCorrect = []

    for (var i = 0; i < len; i++) {
      var item = list[i]

      listCorrect.push({
        verticalGain: item.verticalGain,
        verticalAngel: item.verticalAngel
      })
    }

    return listCorrect
  }

  function getRadarData(list, type, KeyMap) {
    var data = []
    var len = list.length

    for (var i = 0; i < len; i++) {
      var item = list[i]

      data.push([item[KeyMap[type]['gain']], item[KeyMap[type]['angel']]])
    }

    return data
  }

  function normalizeByMax(obj) {
    var data = obj.body.data,
      sMax = +obj.body.data.max,
      name = obj.body.data.name

    var sData = {
      success: true,
      errorCode: -1,
      msg: '请求成功',
      body: {
        data: {
          name: name,
          max: sMax,
          cmdiBeamHGainList: [],
          cmdiBeamVGainList: []
        }
      }
    }

    sData.body.data.cmdiBeamHGainList = data.cmdiBeamHGainList.map(function (
      item
    ) {
      return {
        horizontalAngel: +item.horizontalAngel,
        horizontalGain: sMax - +item.horizontalGain
      }
    })

    sData.body.data.cmdiBeamVGainList = data.cmdiBeamVGainList.map(function (
      item
    ) {
      return {
        verticalAngel: +item.verticalAngel,
        verticalGain: sMax - +item.verticalGain
      }
    })

    return sData
  }

  function getGainArr(obj) {
    var parametricEquation = obj.parametricEquation || {}
    var uOpts = parametricEquation.u || {}
    var vOpts = parametricEquation.v || {}
    var hgain = obj.hgain
    var vgain = obj.vgain
    var method = obj.method

    var uMax = uOpts.max,
      uMin = uOpts.min,
      uStep = uOpts.step,
      vMax = vOpts.max,
      vMin = vOpts.min,
      vStep = vOpts.step

    var uCount = Math.floor((uMax + uStep - uMin) / uStep)
    var vCount = Math.floor((vMax + vStep - vMin) / vStep)

    var gains = []
    for (var j = 0; j < vCount; j++) {
      for (var i = 0; i < uCount; i++) {
        var u = i * uStep + uMin
        var v = j * vStep + vMin
        var gain = Gain[method](u, v, hgain, vgain)

        gains.push(gain)
      }
    }

    return gains
  }

  function resample(points) {
    // 将d3生成的多边形再优化：返回一个3倍于原来的多边形顶点数量的新多边形
    if (!points) {
      return [];
    }

    var points2 = [];
    var n = points.length;

    var i = -1;
    var p0 = points[n - 1];
    
    if (!p0) {
      return [];
    }

    var x0 = p0[0];
    var y0 = p0[1];
    var p1;
    var x1;
    var y1;

    while (++i < n) {;
      (p1 = points[i]), (x1 = p1[0]), (y1 = p1[1])
      points2.push(
        [(x0 * 2 + x1) / 3, (y0 * 2 + y1) / 3],
        [(x0 + x1 * 2) / 3, (y0 + y1 * 2) / 3],
        p1
      );
      (p0 = p1), (x0 = x1), (y0 = y1)
    }

    return points2
  }

  /**
   * 计算地图中多个点的中心坐标
   * @param [array] points 点，示例：[{x: 100.100, y: 20.100}, {x: 100.101, y: 20.101}], x为经度，y为经度
   * @return [boolean] false OR [object] 示例：{lat: 20.102, lng: 100.102} {纬度, 经度}
   */
  function getCenter(points) {
    if (!(points instanceof Array)) {
      throw new Error('参数类型错误 param type error')
    }

    var length = points.length

    if (length === 0) {
      // 传递的参数（数组）长度为0
      return false
    } else {
      var sPoints = points

      if (N.Util.isArray(points[0])) {
        var sPoints = []

        // 如果points[0]为数组 [100.102, 20.102], 则将其适配为{x: 100.100, y: 20.100}形式
        for (var j = 0; j < length; j++) {
          sPoints.push({
            x: points[j][0],
            y: points[j][1]
          })
        }
      } else if (N.Util.isObject(points[0]) && points[0].lat && points[0].lng) {
        // {lat: 100.102, lng: 20.102}形式
        var sPoints = []

        for (var j = 0; j < length; j++) {
          sPoints.push({
            x: points[j].lng,
            y: points[j].lat
          })
        }
      } else if (N.Util.isObject(points[0]) && points[0].lat && points[0].lon) {
        // {lat: 20.102, lng: 100.102}形式
        var sPoints = []

        for (var j = 0; j < length; j++) {
          sPoints.push({
            x: points[j].lon,
            y: points[j].lat
          })
        }
      }

      var maxY = sPoints[0].y,
        maxX = sPoints[0].x,
        minY = sPoints[0].y,
        minX = sPoints[0].x

      for (var i = 1, point;
        (point = sPoints[i]); i++) {
        // 找出最大、最小经纬度
        if (maxY < point.y) {
          maxY = point.y
        }
        if (maxX < point.x) {
          maxX = point.x
        }
        if (minY > point.y) {
          minY = point.y
        }
        if (minX > point.x) {
          minX = point.x
        }
      }

      // 返回中心点经纬度
      return {
        lat: (maxY + minY) / 2,
        lng: (maxX + minX) / 2
      }
    }
  }

  /**
   * 根据广东省城市返回坐标
   * @param [string]str 省市字符串，以'/'分割
   * @return [boolean] false OR [object] 示例：{lat: 20.102, lng: 100.102} {纬度, 经度}
   */
  function getCenterByGuangdongCity(str) {
    var guangdongCities = {
      广州市: {lng: 113.30765, lat: 23.120049},
      潮州市: {lng: 116.630076, lat: 23.661812},
      东莞市: {lng: 113.763434, lat: 23.043024},
      佛山市: {lng: 113.134026, lat: 23.035095},
      河源市: {lng: 114.713721, lat: 23.757251},
      惠州市: {lng: 114.410658, lat: 23.11354},
      江门市: {lng: 113.078125, lat: 22.575117},
      揭阳市: {lng: 116.379501, lat: 23.547999},
      茂名市: {lng: 110.931245, lat: 21.668226},
      梅州市: {lng: 116.126403, lat: 24.304571},
      清远市: {lng: 113.040773, lat: 23.698469},
      汕头市: {lng: 116.72865, lat: 23.383908},
      汕尾市: {lng: 115.372924, lat: 22.778731},
      韶关市: {lng: 113.594461, lat: 24.80296},
      深圳市: {lng: 114.025974, lat: 22.546054},
      阳江市: {lng: 111.97701, lat: 21.871517},
      云浮市: {lng: 112.050946, lat: 22.937976},
      湛江市: {lng: 110.365067, lat: 21.257463},
      肇庆市: {lng: 112.479653, lat: 23.078663},
      中山区: {lng: 113.42206, lat: 22.545178},
      珠海市: {lng: 113.562447, lat: 22.256915}
    };
    var arr = str.split('/');
    var province = arr[0];
    var city = arr[1] || province;
    return guangdongCities[city] || getCenterByProvince(city + '/') || guangdongCities['广州'];
  }

  /**
   * 根据省返回坐标
   * @param [string]str 省市字符串，以'/'分割
   * @return [boolean] false OR [object] 示例：{lat: 20.102, lng: 100.102} {纬度, 经度}
   */
  function getCenterByProvince(str) {
    var chinaProvince = {
      北京市: { lng: 116.395645, lat: 39.929986 },
      上海市: { lng: 116.395645, lat: 39.929986 },
      天津市: { lng: 116.395645, lat: 39.929986 },
      重庆市: { lng: 116.395645, lat: 39.929986 },
      安徽省: { lng: 117.216005, lat: 31.859252 },
      福建省: { lng: 117.984943, lat: 26.050118 },
      甘肃省: { lng: 102.457625, lat: 38.103267 },
      广东省: { lng: 113.394818, lat: 23.408004 },
      广西省: { lng: 108.924274, lat: 23.552255 },
      贵州省: { lng: 106.734996, lat: 26.902826 },
      海南省: { lng: 109.733755, lat: 19.180501 },
      河北省: { lng: 115.661434, lat: 38.61384 },
      河南省: { lng: 113.486804, lat: 34.157184 },
      黑龙江省: { lng: 128.047414, lat: 47.356592 },
      湖北省: { lng: 112.410562, lat: 31.209316 },
      湖南省: { lng: 111.720664, lat: 27.695864 },
      江苏省: { lng: 119.368489, lat: 33.013797 },
      江西省: { lng: 115.676082, lat: 27.757258 },
      吉林省: { lng: 126.262876, lat: 43.678846 },
      辽宁省: { lng: 122.753592, lat: 41.6216 },
      内蒙古自治区: { lng: 114.415868, lat: 43.468238 },
      宁夏回族自治区: { lng: 106.155481, lat: 37.321323 },
      青海省: { lng: 96.202544, lat: 35.499761 },
      山东省: { lng: 118.527663, lat: 36.09929 },
      山西省: { lng: 112.515496, lat: 37.866566 },
      陕西省: { lng: 109.503789, lat: 35.860026 },
      四川省: { lng: 102.89916, lat: 30.367481 },
      西藏自治区: { lng: 89.137982, lat: 31.367315 },
      新疆维吾尔自治区: { lng: 85.614899, lat: 42.127001 },
      云南省: { lng: 101.592952, lat: 24.864213 },
      浙江省: { lng: 119.957202, lat: 29.159494 }
    };
    var arr = str.split('/');
    var province = arr[0];
    return chinaProvince[province];
  }
  
  /**
   * 判断某个经纬度坐标是否在geExcludeStationList列表中
   * @param  {object} point
   * @param  {array} arr optional
   * @return {boolean} flag  
   */
  function isPointInArr(point, arr) {
    var flag = false;
    var sArr = arr ? arr : window.geExcludeStationList;
    var count = sArr.length;

    while(--count) {
      if (sArr[count].lat === point.lat && sArr[count].lon === point.lon ) {
        flag = true;
        break;
      }
    }

    return flag;
  }

  var Data = (Object.freeze || Object)({
    getOneDimArray: getOneDimArray,
    getHCorrectList: getHCorrectList,
    getVCorrectList: getVCorrectList,
    getRadarData: getRadarData,
    normalizeByMax: normalizeByMax,
    getGainArr: getGainArr,
    resample: resample,
    getCenter: getCenter,
    getCenterByProvince: getCenterByProvince,
    getCenterByGuangdongCity: getCenterByGuangdongCity,
    isPointInArr: isPointInArr
  })

  /**
   * @param [number] u(alpha) 点连接原点的线在x-y面投影与y轴正方向夹角 弧度  俯视顺时针为正
   * @param [number] v(beta) 点连接原点的线与z轴正方向方向夹角 弧度 侧视时顺时针为正
   * @param [array] hgain 水平方向增益
   * @param [array] vgain 垂直方向增益
   *
   * @return [number] 该点的增益
   */
  function gainZx(u, v, hgain, vgain) {
    //u [-pi,pi]
    //v [pi/2,3*pi/2]
    //ut[-180,180]
    //vt[90,270]->[0,180]
    //ut需要+360进行正值修正，vt不需要正值修正

    var pi = Math.PI
    var t = 180 / pi
    var ut = parseInt((u * t).toFixed(0), 10)
    var vt = parseInt((v * t).toFixed(0), 10)
    if (ut < 0) {
      ut += 360
    }

    //vt -= 90;
    if (vt < 0) {
      vt += 360
    }

    ut = ut === 360 ? 0 : ut
    vt = vt === 360 ? 0 : vt

    var halpha = hgain[ut] // H(alpha)
    var vbeta = vgain[vt]
    var h0 = hgain[0] // H(0)
    var hpi = hgain[180] // H(PI)
    var pi_beta = 180 - vt
    if (pi_beta < 0) {
      pi_beta += 360
    }
    var v_pi_beta = vgain[pi_beta]

    var g =
      halpha -
      (((pi - Math.abs(u)) / pi) * (h0 - vbeta) +
        (Math.abs(u) / pi) * (hpi - v_pi_beta)) // u取绝对值

    if (vt === 0 || vt === 180) {
      g = halpha
    }

    return g
  }

  /**
   * 取天线增益
   * @param {Num} u Phi[deg] 水平角度
   * @param {Num} v Theta[deg] 垂直角度
   * @return {Num} g 增益
   */
  function gain3d(u, v, gainList) {
    var key = u + ',' + v
    var gainList = window.gainList

    return gainList[key]
  }

  var Gain = (Object.freeze || Object)({
    gainZx: gainZx,
    gain3d: gain3d
  })

  function tooltipZx(params, hgain, vgain, max, min) {
    var pi = Math.PI,
      t = 180 / pi
    var u = params.data[3],
      v = params.data[4],
      ut = parseInt((u * t).toFixed(0), 10),
      vt = parseInt((v * t).toFixed(0), 10),
      hg = hgain[ut], // H(alpha)
      vg = vgain[vt], // V(beta)
      antennaGain = gainZx(u, v, hgain, vgain)

    var normalize = (antennaGain - min) / (max - min)

    // params.data[5] = antennaGain;

    ut = ut === 360 ? 0 : ut
    vt = vt === 360 ? 0 : vt

    var dom = ''
    dom +=
      params.seriesName +
      '<br>' +
      params.marker +
      '<br>' +
      '- 水平角度：' +
      ut +
      ' ，增益：' +
      hg +
      '<br>' +
      '- 垂直角度：' +
      vt +
      '，增益：' +
      vg +
      '<br>' +
      '- 增益: ' +
      antennaGain +
      '<br>'
    // '- 最大增益: ' + max + ('<br>') +
    // '- 最小增益: ' + min + ('<br>') +
    // '- 归一化: ' + normalize + ('<br>');

    return dom
  }

  var Formatter = (Object.freeze || Object)({
    gainZx: tooltipZx
  })

  function getGainColor8(x, max, min) {
    var color
    if (x < -60) {
      color = 'rgb(0, 255, 0)'
    } else if (x >= -60 && x < -45) {
      color = 'rgb(0, 255, 0)'
    } else if (x >= -45 && x < -30) {
      color = 'rgb(255, 255, 0)'
    } else if (x >= -30 && x < -15) {
      color = 'rgb(255,165,0)'
    } else if (x >= -15) {
      color = 'rgb(255,0,0)'
    } else {
      color = 'rgb(255,0,0)'
    }

    return color
  }

  // 254,142,32 橙   254,206,36 黄  3,222,254 青   85,137,238 浅蓝   5,69,192 蓝
  function getGainColor4(x, max, min) {
    var color
    if (x < -60) {
      // color = "rgb(0, 255, 0)";
      color = 'rgb(5,69,192)'
    } else if (x >= -60 && x < -45) {
      // color = "rgb(0, 255, 0)";
      color = 'rgb(85,137,238)'
    } else if (x >= -45 && x < -30) {
      // color = "rgb(255, 255, 0)";
      color = 'rgb(3,222,254)'
    } else if (x >= -30 && x < -15) {
      // color = "rgb(255,165,0)";
      color = 'rgb(254,206,36)'
    } else if (x >= -15) {
      // color = "rgb(255,0,0)";
      color = 'rgb(254,142,32)'
    } else {
      // color = "rgb(255,0,0)";
      color = 'rgb(254,142,32)'
    }

    return color
  }

  // 254,142,32 橙   254,206,36 黄  3,222,254 青   85,137,238 浅蓝   5,69,192 蓝
  function getGainColor4One(x, max, min) {
    var s = (x - min) / (max - min)

    var color
    if (s < 0) {
      color = 'rgb(5,69,192)'
    } else if (s >= 0 && s < 0.2) {
      color = 'rgb(5,69,192)'
    } else if (s >= 0.2 && s < 0.4) {
      color = 'rgb(85,137,238)'
    } else if (s >= 0.4 && s < 0.6) {
      color = 'rgb(3,222,254)'
    } else if (s >= 0.6 && s < 0.8) {
      color = 'rgb(254,206,36)'
    } else if (s >= 0.8 && s < 1) {
      color = 'rgb(254,142,32)'
    } else {
      color = 'rgb(254,142,32)'
    }

    return color
  }

  function getGainColorMedium(x, max, min) {
    var sMedium = -30
    var color
    if (x < sMedium) {
      color = 'rgb(0,200,255)'
    } else if (x >= sMedium) {
      color = 'rgb(218, 65, 61)'
    }

    return color
  }

  function getGainColor2(x, max, min) {
    var s = (x - min) / (max - min)
    var color
    if (s < 0) {
      color = 'rgb(0, 150, 255)'
    } else if (s >= 0 && s < 0.5) {
      // color = "rgb(218, 255, 50)";
      color = 'rgb(255, 70, 0)'
    } else if (s >= 0.5 && s < 1) {
      color = 'rgb(180, 0, 0)'
    } else {
      color = 'rgb(255, 0, 0)'
    }

    return color
  }

  function getGainColor5(x, max, min) {
    var s = (x - min) / (max - min)
    var color
    if (s < 0) {
      color = 'rgb(0, 150, 255)'
    } else if (s >= 0 && s < 0.2) {
      color = 'rgb(0, 255, 200)'
    } else if (s >= 0.2 && s < 0.4) {
      color = 'rgb(218, 255, 50)'
    } else if (s >= 0.4 && s < 0.6) {
      color = 'rgb(255, 255, 40)'
    } else if (s >= 0.6 && s < 0.8) {
      color = 'rgb(255, 70, 0)'
    } else if (s >= 0.8 && s < 1) {
      color = 'rgb(180, 0, 0)'
    } else {
      color = 'rgb(255, 0, 0)'
    }

    return color
  }

  function getGainColor10(x, max, min) {
    var s = (x - min) / (max - min)
    var color
    if (s < 0) {
      color = 'rgb(0, 150, 255)'
    } else if (s >= 0 && s < 0.1) {
      color = 'rgb(0, 255, 200)'
    } else if (s >= 0.1 && s < 0.2) {
      color = 'rgb(130, 255, 0)'
    } else if (s >= 0.2 && s < 0.3) {
      color = 'rgb(218, 255, 50)'
    } else if (s >= 0.3 && s < 0.4) {
      color = 'rgb(255, 255, 40)'
    } else if (s >= 0.4 && s < 0.5) {
      color = 'rgb(255, 220, 30)'
    } else if (s >= 0.5 && s < 0.6) {
      color = 'rgb(255, 160, 0)'
    } else if (s >= 0.6 && s < 0.7) {
      color = 'rgb(255, 70, 0)'
    } else if (s >= 0.7 && s < 0.8) {
      color = 'rgb(255, 20, 0)'
    } else if (s >= 0.8 && s < 0.9) {
      color = 'rgb(180, 0, 0)'
    } else if (s >= 0.9 && s < 1) {
      color = 'rgb(160, 0, 0)'
    } else {
      color = 'rgb(255, 0, 0)'
    }

    return color
  }

  function getGainColor(x, max, min) {
    var s = (x - min) / (max - min)
    var color
    if (s < 0) {
      color = 'rgb(0, 0, 255)'
    } else if (s >= 0 && s < 0.02) {
      color = 'rgb(0, 0, 255)'
    } else if (s >= 0.02 && s < 0.04) {
      color = 'rgb(0, 30, 255)'
    } else if (s >= 0.04 && s < 0.06) {
      color = 'rgb(0, 80, 255)'
    } else if (s >= 0.06 && s < 0.08) {
      color = 'rgb(0, 150, 255)'
    } else if (s >= 0.08 && s < 0.1) {
      color = 'rgb(0, 180, 255)'
    } else if (s >= 0.1 && s < 0.12) {
      color = 'rgb(0, 210, 255)'
    } else if (s >= 0.12 && s < 0.14) {
      color = 'rgb(0, 255, 255)'
    } else if (s >= 0.14 && s < 0.16) {
      color = 'rgb(0, 255, 220)'
    } else if (s >= 0.16 && s < 0.18) {
      color = 'rgb(0, 255, 200)'
    } else if (s >= 0.18 && s < 0.2) {
      color = 'rgb(0, 255, 90)'
    } else if (s >= 0.2 && s < 0.22) {
      color = 'rgb(0, 255, 60)'
    } else if (s >= 0.22 && s < 0.24) {
      color = 'rgb(0, 255, 30)'
    } else if (s >= 0.24 && s < 0.26) {
      color = 'rgb(100, 255, 0)'
    } else if (s >= 0.26 && s < 0.28) {
      color = 'rgb(130, 255, 0)'
    } else if (s >= 0.28 && s < 0.3) {
      color = 'rgb(180, 255, 0)'
    } else if (s >= 0.3 && s < 0.32) {
      color = 'rgb(180, 255, 0)'
    } else if (s >= 0.32 && s < 0.34) {
      color = 'rgb(180, 255, 0)'
    } else if (s >= 0.34 && s < 0.36) {
      color = 'rgb(210, 255, 0)'
    } else if (s >= 0.36 && s < 0.38) {
      color = 'rgb(218, 255, 50)'
    } else if (s >= 0.38 && s < 0.4) {
      color = 'rgb(220, 255, 75)'
    } else if (s >= 0.4 && s < 0.42) {
      color = 'rgb(255, 255, 120)'
    } else if (s >= 0.42 && s < 0.44) {
      color = 'rgb(255, 255, 80)'
    } else if (s >= 0.44 && s < 0.46) {
      color = 'rgb(255, 255, 60)'
    } else if (s >= 0.46 && s < 0.48) {
      color = 'rgb(255, 255, 40)'
    } else if (s >= 0.48 && s < 0.5) {
      color = 'rgb(255, 255, 20)'
    } else if (s >= 0.5 && s < 0.52) {
      color = 'rgb(255, 220, 100)'
    } else if (s >= 0.52 && s < 0.54) {
      color = 'rgb(255, 220, 80)'
    } else if (s >= 0.54 && s < 0.56) {
      color = 'rgb(255, 220, 60)'
    } else if (s >= 0.56 && s < 0.58) {
      color = 'rgb(255, 220, 30)'
    } else if (s >= 0.58 && s < 0.6) {
      color = 'rgb(255, 220, 0)'
    } else if (s >= 0.6 && s < 0.62) {
      color = 'rgb(255, 200, 0)'
    } else if (s >= 0.62 && s < 0.64) {
      color = 'rgb(255, 180, 0)'
    } else if (s >= 0.64 && s < 0.66) {
      color = 'rgb(255, 170, 0)'
    } else if (s >= 0.66 && s < 0.68) {
      color = 'rgb(255, 160, 0)'
    } else if (s >= 0.68 && s < 0.7) {
      color = 'rgb(255, 140, 0)'
    } else if (s >= 0.7 && s < 0.72) {
      color = 'rgb(255, 100, 0)'
    } else if (s >= 0.72 && s < 0.74) {
      color = 'rgb(255, 90, 0)'
    } else if (s >= 0.74 && s < 0.76) {
      color = 'rgb(255, 80, 0)'
    } else if (s >= 0.76 && s < 0.78) {
      color = 'rgb(255, 70, 0)'
    } else if (s >= 0.78 && s < 0.8) {
      color = 'rgb(255, 60, 0)'
    } else if (s >= 0.8 && s < 0.82) {
      color = 'rgb(255, 50, 0)'
    } else if (s >= 0.82 && s < 0.84) {
      color = 'rgb(255, 40, 0)'
    } else if (s >= 0.84 && s < 0.86) {
      color = 'rgb(255, 30, 0)'
    } else if (s >= 0.86 && s < 0.88) {
      color = 'rgb(255, 20, 0)'
    } else if (s >= 0.88 && s < 0.9) {
      color = 'rgb(255, 10, 0)'
    } else if (s >= 0.9 && s < 0.92) {
      color = 'rgb(255, 0, 0)'
    } else if (s >= 0.92 && s < 0.94) {
      color = 'rgb(220, 0, 0)'
    } else if (s >= 0.94 && s < 0.96) {
      color = 'rgb(200, 0, 0)'
    } else if (s >= 0.96 && s < 0.98) {
      color = 'rgb(180, 0, 0)'
    } else if (s >= 0.98 && s < 1) {
      color = 'rgb(160, 0, 0)'
    } else {
      color = 'rgb(255, 0, 0)'
    }

    return color
  }

  // d5ff2a f8f707
  function getGainColorNew10(x, max, min) {
    var s = (x - min) / (max - min)
    var color
    if (s < 0) {
      color = '#0f36a2'
      // color = "rgba(15, 54, 162, 0.8)";
      // color = "rgba(15, 54, 162, 0.8)";
    } else if (s >= 0 && s < 0.1) {
      color = '#0f36a2'
      // color = "rgb(15, 54, 162)";
      // color = "rgba(15, 54, 162, 0.8)";
    } else if (s >= 0.1 && s < 0.2) {
      // color = "#34bf91";
      // color = "#40ffb6";
      color = '#84ff55'
      // color = "rgba(132, 255, 85, 0.8)";
    } else if (s >= 0.2 && s < 0.3) {
      // color = "#40f7f4";
      color = '#57ff59'
      // color = "rgba(87, 255, 89, 0.8)";
    } else if (s >= 0.3 && s < 0.4) {
      // color = "#40ffb6";
      // color = "#84ff55";
      color = '#afff4a'
      // color = "rgba(175, 255, 74, 0.8)";
    } else if (s >= 0.4 && s < 0.5) {
      // color = "#40ff69";
      // color = "#84ff55";
      color = '#d5ff2a'
      // color = "rgba(175, 255, 74, 0.8)";
    } else if (s >= 0.5 && s < 0.6) {
      // color = "#d5ff2a";
      color = '#ffbe00'
      // color = "rgba(255, 190, 0, 0.8)";
    } else if (s >= 0.6 && s < 0.7) {
      // color = "#84ff55";
      color = '#ffbe00'
      // color = "rgba(255, 190, 0, 0.8)";
    } else if (s >= 0.7 && s < 0.8) {
      // color = "#afff4a";
      color = '#ff6300'
      // color = "rgba(255, 99, 0, 0.8)";
    } else if (s >= 0.8 && s < 0.9) {
      // color = "#ff6300";
      color = '#fb1100'
      // color = "rgba(251, 17, 0, 0.8)";
    } else if (s >= 0.9 && s < 1) {
      color = '#8d0100'
      // color = "rgba(141, 1, 0, 0.8)";
    } else if (s >= 1) {
      // color = "#fb1100";
      color = '#8d0100'
      // color = "rgba(141, 1, 0, 0.8)";
    }

    return color
  }

  // ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
  function getGainColorNewEcharts(x, max, min) {
    var s = (x - min) / (max - min)
    var color
    if (s < 0) {
      color = '#313695'
    } else if (s >= 0 && s < 0.1) {
      color = '#313695'
    } else if (s >= 0.1 && s < 0.2) {
      color = '#4575b4'
    } else if (s >= 0.2 && s < 0.3) {
      color = '#74add1'
    } else if (s >= 0.3 && s < 0.4) {
      color = '#abd9e9'
    } else if (s >= 0.4 && s < 0.5) {
      color = '#e0f3f8'
      // color = "#f46d43";
    } else if (s >= 0.5 && s < 0.6) {
      color = '#ffffbf'
    } else if (s >= 0.6 && s < 0.7) {
      color = '#fee090'
    } else if (s >= 0.7 && s < 0.8) {
      color = '#fdae61'
    } else if (s >= 0.8 && s < 0.9) {
      color = '#f46d43'
    } else if (s >= 0.9 && s < 1) {
      color = '#d73027'
    } else if (s >= 1) {
      color = '#a50026'
    }

    return color
  }

  function getGainColorBlue(x, max, min) {
    var s = (x - min) / (max - min)
    // var s = (x + 60) / (max + 60);
    var color
    if (s < 0) {
      color = '#ADBAFF'
    } else if (s >= 0 && s < 0.1) {
      color = '#ADBAFF'
    } else if (s >= 0.1 && s < 0.2) {
      color = '#ACBAFF'
    } else if (s >= 0.2 && s < 0.3) {
      color = '#93A1EA'
    } else if (s >= 0.3 && s < 0.4) {
      color = '#7180CC'
    } else if (s >= 0.4 && s < 0.5) {
      color = '#4F60B0'
    } else if (s >= 0.5 && s < 0.6) {
      color = '#314191'
    } else if (s >= 0.6 && s < 0.7) {
      color = '#202F78'
    } else if (s >= 0.7 && s < 0.8) {
      color = '#162360'
    } else if (s >= 0.8 && s < 0.9) {
      color = '#101949'
    } else if (s >= 0.9 && s < 1) {
      color = '#0B1338'
    } else if (s >= 1) {
      color = '#0B1338'
    }

    return color
  }

  function getVoronoiColor(f) {
    let color = 'rgba(124,205,124, 1)'

    if (f >= 15) {
      color = 'rgba(0,100,0, 1)'
    } else if (10 <= f && f < 15) {
      color = 'rgba(46,139,87, 1)'
    } else if (5 <= f && f < 10) {
      color = 'rgba(30,172,30, 1)'
    } else if (0 <= f && f < 5) {
      color = 'rgba(60,179,113, 1)'
    }

    return color
  }

  function getOpacity(polygons) {
    let r = .3;

    if (!polygons) {
      r = 0;
    } else {
      var isOut = polygons.some(function (item) {
        return (Math.max(Math.abs(item[0]), Math.abs(item[1])) > 3000);
      });

      if (isOut) {
        r = 0;
      }
    }

    return r;
  }

  var Color = (Object.freeze || Object)({
    getGainColor: getGainColor4,
    getVoronoiColor: getVoronoiColor,
    getOpacity: getOpacity
  })

  function toggleBtn(id, klass) {
    const $ele = $(`#${id}`),
      $siblings = $ele.siblings()

    $ele.addClass(klass)
    $siblings.removeClass(klass)
  }

  function getActiveBtn(sel) {
    const $ele = $(sel),
      $parent = $ele.parent(),
      $all = $parent.children()

    return $all.filter('.active').data('content')
  }

  function getSearchParam(id) {
    const $ele = $(`#${id}`),
      text = $ele.val().trim()

    return text
  }

  /**
   * 注意：此函数依赖于jQuery
   * 分页列表无数据时设置dom
   * @param $page 包含匹配html元素的jquery对象
   * @param $list 包含匹配html元素的jquery对象
   * @return 无
   */
  function setEmptyDom($page, $list) {
    const $dom = $(`<div class="empty-text">
      <div class="empty-list-icon"></div>
      </div>`)

    $page.html('')
    $list.html($dom)

    return
  }

  function setCloseBtnStyle(layerSel) {
    const $closeBtn = $(layerSel)
      .parent()
      .siblings('.layui-layer-setwin')

    $closeBtn.css({
      zIndex: 2
    })

    $closeBtn
      .children('.layui-layer-ico')
      .removeClass('layui-layer-close2')
      .addClass('layui-layer-close1')
  }

  // 生成legend dom
  function getColorLegend(colors) {
    if (!isArray(colors)) {
      throw new Error('param type error 参数不是数组')
    }

    var length = colors.length,
      labels = ''

    if (length > 0) {
      for (var i = 0; i < length; i++) {
        labels +=
          '<div><i style="background:' +
          colors[i][0] +
          '"></i><span>' +
          colors[i][1] +
          '</span></div>'
      }
    }

    return labels
  }

  var Dom = (Object.freeze || Object)({
    toggleBtn: toggleBtn,
    getActiveBtn: getActiveBtn,
    getSearchParam: getSearchParam,
    setCloseBtnStyle: setCloseBtnStyle,
    setEmptyDom: setEmptyDom,
    getColorLegend: getColorLegend
  })

  function getMaxHeight(list) {
    var copyList = JSON.parse(JSON.stringify(list)),
      len = copyList.length

    // 按天线高度对copyList排序，从小到大
    copyList.sort(function (a, b) {
      if (a.ah < b.ah) {
        return -1
      } else if (a.ah > b.ah) {
        return 1
      }

      return 0
    })

    console.log(copyList, list, copyList[len - 1].ah)

    return copyList[len - 1].ah
  }

  // @function initCesiumViewer(id: String): Object
  // 初始化cesium地图，返回viewer
  function initCesiumViewer(id) {
    var viewer = new Cesium.Viewer(id, {
      animation: false, //是否显示动画控件
      baseLayerPicker: true, //是否显示图层选择控件
      geocoder: true, //是否显示地名查找控件
      timeline: false, //是否显示时间线控件
      sceneModePicker: true, //是否显示投影方式控件
      selectionIndicator: true,
      navigationHelpButton: true, //是否显示帮助信息控件
      infoBox: true, //是否显示点击要素之后显示的信息
      projectionPicker: true,
      requestRenderMode: true,
      maximumRenderTimeChange: Infinity,
      vrButton: false,
      skyAtmosphere: new Cesium.SkyAtmosphere(),
      sceneMode: Cesium.SceneMode.SCENE3D,
      mapProjection: new Cesium.WebMercatorProjection(), // 2D时的投影方式
      // creditContainer: document.getElementById('credit')
      requestRenderMode: true,
      // terrainProvider : Cesium.createWorldTerrain(),
      imageryProvider: new Cesium.WebMapTileServiceImageryProvider({
        url: N.Constant.tianmapProvider.normal.map,
        layer: 'img',
        style: 'default',
        format: 'tiles',
        tileMatrixSetID: 'w',
        credit: new Cesium.Credit('天地图全球影像服务'),
        subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'],
        maximumLevel: 18
      }),
      baseLayerPicker: false
    })

    viewer.imageryLayers.addImageryProvider(
      new Cesium.WebMapTileServiceImageryProvider({
        url: N.Constant.tianmapProvider.normal.annotion,
        layer: 'tdtAnnoLayer',
        style: 'default',
        format: 'image/jpeg',
        tileMatrixSetID: 'GoogleMapsCompatible'
      })
    )

    Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
      70.3618,
      3.52,
      138.8589,
      53.7376
    )

    // viewer.extend(Cesium.viewerCesiumInspectorMixin);
    // viewer.scene.screenSpaceCameraController.minimumZoomDistance = 2000; // 2km
    viewer.scene.screenSpaceCameraController.minimumZoomDistance = 2
    viewer.scene.screenSpaceCameraController.maximumZoomDistance = 22000000 // 2万km

    document.querySelector('.cesium-widget-credits').style = 'display:none;'

    return viewer
  }

  function selectedEntity(viewer) {
    // Information about the currently selected feature
    var selected = {
      feature: undefined,
      originalColor: new Cesium.Color()
    }

    // Information about the currently highlighted feature
    var highlighted = {
      feature: undefined,
      originalColor: new Cesium.Color()
    }

    // An entity object which will hold info about the currently selected feature for infobox display
    var selectedEntity = new Cesium.Entity()

    // Color a feature on selection and show metadata in the InfoBox.
    var clickHandler = viewer.screenSpaceEventHandler.getInputAction(
      Cesium.ScreenSpaceEventType.LEFT_CLICK
    )
    viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(
        movement
      ) {
        // If a feature was previously selected, undo the highlight
        if (Cesium.defined(selected.feature)) {
          if (
            selected.feature._batchId &&
            selected.feature._content._batchTable
          ) {
            // 3dtile
            selected.feature.color = selected.originalColor
          } else if (selected.feature.id && selected.feature.id.model) {
            // gltf
            selected.feature.id.model.color = selected.originalColor
          } else {
            // geojson
            // selected.feature.color = selected.originalColor;
          }

          selected.feature = undefined
        }

        // Pick a new feature
        var pickedFeature = viewer.scene.pick(movement.position)
        if (!Cesium.defined(pickedFeature)) {
          clickHandler(movement)
          return
        }

        // Select the feature if it's not already selected
        if (selected.feature === pickedFeature) {
          return
        }
        selected.feature = pickedFeature

        // Save the selected feature's original color
        if (pickedFeature === highlighted.feature) {
          Cesium.Color.clone(highlighted.originalColor, selected.originalColor)
          highlighted.feature = undefined
        } else {
          Cesium.Color.clone(pickedFeature.color, selected.originalColor)
        }

        // Highlight newly selected feature
        if (pickedFeature instanceof Cesium.Cesium3DTileFeature) {
          // 3dtile
          pickedFeature.color = Cesium.Color.LIME

          // Set feature infobox description
          var featureName = pickedFeature.getProperty('name')
          selectedEntity.name = featureName
          selectedEntity.description =
            'Loading <div class="cesium-infoBox-loading"></div>'
          viewer.selectedEntity = selectedEntity
          selectedEntity.description =
            '<table class="cesium-infoBox-defaultTable"><tbody>' +
            '<tr><th>BIN</th><td>' +
            pickedFeature.getProperty('BIN') +
            '</td></tr>' +
            '<tr><th>DOITT ID</th><td>' +
            pickedFeature.getProperty('DOITT_ID') +
            '</td></tr>' +
            '<tr><th>SOURCE ID</th><td>' +
            pickedFeature.getProperty('SOURCE_ID') +
            '</td></tr>' +
            '<tr><th>Longitude</th><td>' +
            pickedFeature.getProperty('longitude') +
            '</td></tr>' +
            '<tr><th>Latitude</th><td>' +
            pickedFeature.getProperty('latitude') +
            '</td></tr>' +
            '<tr><th>Height</th><td>' +
            pickedFeature.getProperty('height') +
            '</td></tr>' +
            '<tr><th>Terrain Height (Ellipsoid)</th><td>' +
            pickedFeature.getProperty('TerrainHeight') +
            '</td></tr>' +
            '</tbody></table>'
        } else if (pickedFeature.id.model) {
          // gltf
          pickedFeature.id.model.color = Cesium.Color.LIME

          // Set feature infobox description
          var featureName = pickedFeature.id._name
          selectedEntity.name = featureName
          selectedEntity.description = pickedFeature.id._description
        } else {
          // geojson
          pickedFeature.color = Cesium.Color.LIME
        }

        viewer.selectedEntity = selectedEntity

        // if (pickedFeature) {
        //     // Set feature infobox description
        //     // var featureName = pickedFeature.id._name;
        //     // selectedEntity.name = featureName;
        //     // selectedEntity.description = pickedFeature.id._description;
        //     viewer.selectedEntity = selectedEntity;
        // }
      },
      Cesium.ScreenSpaceEventType.LEFT_CLICK)
  }

  function getNameOverlay() {
    // HTML overlay for showing feature name on mouseover
    var nameOverlay = document.createElement('div')
    viewer.container.appendChild(nameOverlay)
    nameOverlay.className = 'backdrop'
    nameOverlay.style.display = 'none'
    nameOverlay.style.position = 'absolute'
    nameOverlay.style.bottom = '0'
    nameOverlay.style.left = '0'
    nameOverlay.style['pointer-events'] = 'none'
    nameOverlay.style.padding = '4px'
    nameOverlay.style.backgroundColor = 'black'

    return nameOverlay
  }

  function mouseMoveEntity(viewer) {
    // Information about the currently highlighted feature
    var highlighted = {
      feature: undefined,
      originalColor: new Cesium.Color()
    }

    var selected = {
      feature: undefined,
      originalColor: new Cesium.Color()
    }

    var nameOverlay = getNameOverlay()

    // Color a feature yellow on hover.
    viewer.screenSpaceEventHandler.setInputAction(function onMouseMove(
        movement
      ) {
        // If a feature was previously highlighted, undo the highlight
        if (Cesium.defined(highlighted.feature)) {
          if (highlighted.feature._content._batchTable) {
            highlighted.feature.color = highlighted.originalColor
            highlighted.feature = undefined
          }
        }

        // Pick a new feature
        var pickedFeature = viewer.scene.pick(movement.endPosition)
        if (pickedFeature instanceof Cesium.Cesium3DTileFeature) {
          if (!Cesium.defined(pickedFeature)) {
            nameOverlay.style.display = 'none'
            return
          }

          // A feature was picked, so show it's overlay content
          nameOverlay.style.display = 'block'
          nameOverlay.style.bottom =
            viewer.canvas.clientHeight - movement.endPosition.y + 'px'
          nameOverlay.style.left = movement.endPosition.x + 'px'
          if (pickedFeature.getProperty) {
            var name = pickedFeature.getProperty('name')
            if (!Cesium.defined(name)) {
              name = pickedFeature.getProperty('id')
            }
            nameOverlay.textContent = name

            // Highlight the feature if it's not already selected.
            if (pickedFeature !== selected.feature) {
              highlighted.feature = pickedFeature
              Cesium.Color.clone(pickedFeature.color, highlighted.originalColor)
              pickedFeature.color = Cesium.Color.YELLOW
            }
          }
        }
      },
      Cesium.ScreenSpaceEventType.MOUSE_MOVE)
  }

  function limitHeightShowGltf(viewer, height) {
    var entities = viewer.entities
    // var scrollHandler = viewer.screenSpaceEventHandler.getInputAction(Cesium.ScreenSpaceEventType.WHEEL);
    viewer.screenSpaceEventHandler.setInputAction(function onScroll() {
      var ellipsoid = viewer.scene.globe.ellipsoid
      var cartesian3Position = viewer.scene.camera.position
      var cartographic = ellipsoid.cartesianToCartographic(cartesian3Position)
      var alt = cartographic.height

      if (alt > height) {
        entities.show = false
      } else {
        entities.show = true
      }
    }, Cesium.ScreenSpaceEventType.WHEEL)
  }

  function flyToHeadingPitchRoll(viewer, opt) {
    var longitude = opt.longitude || 108.95681521053733
    var latitude = opt.latitude || 34.276757630125125
    var height = opt.height || 70
    var heading = opt.heading || 0
    var position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height)

    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
      orientation: {
        heading: 0,
        pitch: Cesium.Math.toRadians(-35.0),
        roll: 0.0
      }
    })
  }

  function getPosition(viewer) {
    var scene = viewer.scene
    var canvas = scene.canvas
    var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas)

    handler.setInputAction(function (movement) {
      var cartesian = scene.camera.pickEllipsoid(
        movement.endPosition,
        ellipsoid
      )
      var ellipsoid = scene.globe.ellipsoid

      if (cartesian) {
        //能获取，显示坐标
        var cartographic = ellipsoid.cartesianToCartographic(cartesian)
        var coords =
          '经度' +
          Cesium.Math.toDegrees(cartographic.longitude).toFixed(2) +
          ', ' +
          '纬度' +
          Cesium.Math.toDegrees(cartographic.latitude).toFixed(2) +
          '' +
          '高度 ' +
          Math.ceil(viewer.camera.positionCartographic.height)
        document.getElementById('coords').innerHTML = `坐标：${coords}`
        document.getElementById('coords').style.display = ''
      } else {
        //不能获取不显示
        document.getElementById('coords').style.display = 'none'
      }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
  }

  function limitCameraHeight(viewer, height) {
    var camera = viewer.camera
    var postRenderEventListener = viewer.scene.postRender.addEventListener(
      function () {
        if (camera.positionCartographic.height < height) {
          var ellipsoid = viewer.scene.globe.ellipsoid
          var cartesianPosition = ellipsoid.cartesianToCartographic(
            camera.position
          )
          var cartesian3 = ellipsoid.cartographicToCartesian(
            new Cesium.Cartographic(
              cartesianPosition.longitude,
              cartesianPosition.latitude,
              height
            )
          )
          viewer.camera.setView({
            destination: cartesian3,
            orientation: {
              heading: camera.heading,
              pitch: camera.pitch,
              roll: camera.roll
            }
          })
        }
      }
    )
    return postRenderEventListener
  }

  /**
   * @viewer [object] Cesium viewer
   * @data [array] 基站列表
   * @limitCount [number]
   * @stationGltf [string] 基站模型文件url地址
   * @antennaGltf [string] 扇区模型文件url地址
   * @retrun
   */
  function loadGltf(viewer, data, limitCount, stationGltf, antennaGltf) {
    var entities = viewer.entities,
      stations = data,
      stationLength = limitCount ? limitCount : stations.length,
      stationGltf =
      stationGltf ||
      Page.staticPath +
      '/common/3ddata/3d-model/tower-section-20180705-2/gan.gltf',
      antennaGltf =
      antennaGltf ||
      Page.staticPath +
      '/common/3ddata/3d-model/tower-section-20180705-2/shanqu.gltf'

    var pitch = Cesium.Math.toRadians(0.0),
      roll = 0.0

    for (var i = 0; i < stationLength; i++) {
      var station = stations[i]
      var antennas = station.antennas || station.a
      var antennaLength = antennas.length
      var lng = station.position ? station.position.lng : station.x
      var lat = station.position ? station.position.lat : station.y

      // 先获取天线列表中最大的高度
      var maxAntennaHeight = getMaxHeight(antennas)

      var stationHeight = station.height ? station.height : maxAntennaHeight + 1
      var circle = {
        name: '基站',
        position: Cesium.Cartesian3.fromDegrees(lng, lat, 0.0),
        ellipse: {
          semiMinorAxis: 100.0,
          semiMajorAxis: 100.0,
          material: Cesium.Color.RED.withAlpha(0.5)
        }
      }

      // 添加容易识别的圆形
      // !entities.contains(circle) ? entities.add(circle) : '';
      entities.add(circle)

      entities.add({
        name: station.n || station.id,
        position: Cesium.Cartesian3.fromDegrees(lng, lat, 0),
        show: true,
        model: {
          uri: stationGltf,
          scale: (stationHeight || 40) / 46.687
          // scale: 200,
        },
        description: `<p>基站名称：${station.n}</p>
                                <p>经度：${lng}</p>
                                <p>纬度：${lat}</p>`
        // billboard: {
        //     image: '../../public/img/billboard.png',
        //     width: 64,
        //     height: 64,
        //     show: false,
        //     distanceDisplayCondition: 10
        // }
      })

      for (var j = 0; j < antennaLength; j++) {
        var antenna = antennas[j]
        var height = 40
        var angle = antenna

        if (isObject(antenna)) {
          height = antenna.ah
          angle = antenna.ad
        } else {
          angle = antenna
        }
        var antennaPosition = Cesium.Cartesian3.fromDegrees(
          lng,
          lat,
          height - 11.48 / 2
        )
        // var antennaPosition = Cesium.Cartesian3.fromDegrees(lng, lat, (height - 11.48 / 2) * 200);
        var heading = Cesium.Math.toRadians(angle - 90) // 155
        var headingPitchRoll = new Cesium.HeadingPitchRoll(heading, pitch, roll)

        entities.add({
          name: antenna.an,
          position: antennaPosition,
          orientation: Cesium.Transforms.headingPitchRollQuaternion(
            antennaPosition,
            headingPitchRoll
          ),
          show: true,
          model: {
            uri: antennaGltf
            // scale: 1,
            // scale: 200,
          },
          description: `<p>天线名称：${antenna.an}</p>
                                    <p>天线角度：${angle}</p>
                                    <p>天线高度：${height}</p>`
        })
      }
    }

    // viewer.zoomTo(entities._entities._array[0]);
    flyToHeadingPitchRoll(viewer, {})
  }

  function removeGltf(viewer) {
    viewer.entities.removeAll()
  }

  function removeTileset(viewer, Primitives, filename) {
    if (filename) {
      viewer.scene.primitives.remove(Primitives[filename], true)
    } else {
      viewer.scene.primitives.removeAll()
    }
  }

  /**
   * @viewer [object] Cesium viewer
   * @data [string] 3dtile文件地址
   * @Primitives [object] 用来存储Cesiuim的Primitive实例
   * @filename [string] 3dtile入口文件名
   * @retrun
   */
  function addTileset(viewer, data, Primitives, filename) {
    var tileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: data
        //maximumScreenSpaceError: 2,
        //maximumNumberOfLoadedTiles: 100,
      })
    )

    viewer.scene.primitives.add(tileset)
    Primitives[filename] = tileset

    var loading = jp.loading('正在加载...')
    tileset.readyPromise.then(function (argument) {
      jp.close(loading)

      flyToHeadingPitchRoll(viewer, {})
    })
  }

  function removeDataSource(viewer, DataSource, filename) {
    if (filename) {
      viewer.dataSources.remove(DataSource[filename])
    } else {
      viewer.dataSources.removeAll()
    }
  }

  /**
   * @viewer [object] Cesium viewer
   * @data [string] geojson文件路径
   * @DataSource [object] 用来存储Cesiuim的Primitive实例
   * @fileName[string] geojson文件名
   * @color[string] 图层颜色
   * @retrun
   */
  function addGeojson(obj) {
    var viewer = obj.viewer,
      data = obj.data,
      height = obj.height,
      DataSource = obj.DataSource,
      fileName = obj.fileName,
      color = obj.color

    Cesium.Math.setRandomNumberSeed(0)

    var loading = jp.loading('正在加载...')
    var promise = Cesium.GeoJsonDataSource.load(data)
    promise
      .then(function (dataSource) {
        DataSource[fileName] = dataSource

        viewer.dataSources.add(dataSource)

        //Get the array of entities
        var entities = dataSource.entities.values

        var colorHash = {}
        for (var i = 0; i < entities.length; i++) {
          //For each entity, create a random color based on the state name.
          //Some states have multiple entities, so we store the color in a
          //hash so that we use the same color for the entire state.
          var entity = entities[i]
          //   var name = entity.name;
          //   var color = colorHash[name];
          //   if (!color) {
          //     color = Cesium.Color.fromRandom({
          //       alpha: 1.0
          //     });
          //     colorHash[name] = color;
          //   }

          //Set the polygon material to our random color.
          entity.polygon.material = color
          //Remove the outlines.
          entity.polygon.outline = false

          //Extrude the polygon based on the state's population.  Each entity
          //stores the properties for the GeoJSON feature it was created from
          //Since the population is a huge number, we divide by 50.
          entity.polygon.height = height
        }

        jp.close(loading)
        flyToHeadingPitchRoll(viewer, {})
      })
      .otherwise(function (error) {
        //Display any errrors encountered while loading.
        jp.error('图层文件不存在')
        jp.close(loading)
      })
  }

  var CesiumTool = (Object.freeze || Object)({
    initCesiumViewer: initCesiumViewer,
    loadGltf: loadGltf,
    removeGltf: removeGltf,
    addTileset: addTileset,
    removeTileset: removeTileset,
    addGeojson: addGeojson,
    removeDataSource: removeDataSource,
    limitHeightShowGltf: limitHeightShowGltf,
    selectedEntity: selectedEntity,
    mouseMoveEntity: mouseMoveEntity,
    flyToHeadingPitchRoll: flyToHeadingPitchRoll,
    getPosition: getPosition,
    limitCameraHeight: limitCameraHeight
  })

  function handle2DLayer(obj) {
    var legend = obj.legend
    var $Dom = obj.$Dom
    var $simTree = obj.$simTree
    var menuText = obj.menuText
    var Page = obj.Page
    var colorUrl = obj.colorUrl
    var centerUrl = obj.centerUrl
    var node = obj.node
    var ppNodeName = obj.ppNodeName
    var parentNodeName = obj.parentNodeName

    // 先删除图层色值段图例
    legend.remove()

    // 图层
    $Dom.anaMapBox.show()

    // 如果是图层, 请求图层服务

    var sibs = $simTree.treeview('getSiblings', node),
      length = sibs.length

    for (var i = 0; i < length; i++) {
      $simTree.treeview('uncheckNode', [
        sibs[i].nodeId,
        {
          silent: true
        }
      ])
    }

    // 图层名称
    var layerName = menuText.substring(6).trim(), // 图层名称
      //   layerTitle = menuName,
      wmsUrl = Page.geoServerUrl,
      layer = Page.projId + ':' + layerName

    // 异步请求段值-色值
    $.ajax({
      url: colorUrl,
      method: 'GET',
      data: {
        layerName: layerName
      },
      dataType: 'json',
      success: function (data) {
        if (data.code === 1) {
          console.log('请求失败，返回码：' + data.code, '返回信息：' + data.msg)
        } else if (data.code === 0) {
          // 请求成功，设置段值－色值颜色表
          var colors = data.data,
            length = colors.length

          if (length === 0) {
            console.log('数据为空')

            legend.colors = []
          } else if (length > 15) {
            console.log('色值段超过15')

            legend.colors = []
          } else {
            var sColors = []

            if (menuText.search(/IsPublicCov/) !== -1) {
              // 公共信道覆盖预测是否成功的图例数据适配
              for (var i = 0; i < length; i++) {
                sColors.push({
                  color: colors[i].color,
                  quantity: colors[i].quantity === '0' ? '未覆盖' : '覆盖'
                })
              }
            } else if (menuText.search(/LTEWeakCover/) !== -1) {
              // 弱覆盖的图例数据适配
              for (var i = 0; i < length; i++) {
                sColors.push({
                  color: colors[i].color,
                  quantity: colors[i].quantity === '0' ? '弱覆盖区域' : '达标区域'
                })
              }
            } else {
              sColors = colors
            }

            legend.colors = sColors
          }

          legend.onAdd = function (map) {
            var colors = legend.colors

            return MapUtil.colorLegend(colors)
          }

          legend.addTo(simMap)
        }
      },
      error: function (jqxhr) {
        console.log(
          '请求' + this.url + '时出错',
          '返回码: ' + jqxhr.status,
          '返回信息: ' + jqxhr.responseText.trim()
        )
      }
    })

    layer01 = L.tileLayer.wms(wmsUrl, {
      layers: layer,
      format: 'image/png',
      transparent: true,
      zoomOffset: 2,
      zIndex: 4
      // opacity: 0.5
      // crs: L.CRS.EPSG4326
    })

    if (
      parentNodeName.indexOf('多边形') !== -1 &&
      ppNodeName.indexOf('公共') === -1
    ) {
      layerPolygon = L.tileLayer.wms(wmsUrl, {
        layers: layer,
        format: 'image/png',
        transparent: true,
        zoomOffset: 2,
        zIndex: 4
        // opacity: 0.5
        // crs: L.CRS.EPSG4326
      })
    }

    $.ajax({
      url: centerUrl,
      method: 'GET',
      dataType: 'json',
      data: {
        projectId: Page.projId
      },
      success: function (data) {
        console.log(data)

        if (data.code === 1) {
          // 请求出错
          console.log('请求失败，返回码：' + data.code, '返回信息：' + data.msg)

          simMap.setView({
              lng: 107.9,
              lat: 35.84
            },
            4
          )
        } else {
          var center = data.data

          // simMap.setView(center, 12);
          simMap.setView(center, 14)

          simMap.eachLayer(function (layer) {
            console.log(layer)
            if (layer instanceof L.TileLayer) {
              // console.log(layer);

              if (layer.wmsParams) {
                // 将天地图之外的自定义tilelayer删除
                simMap.removeLayer(layer)
              }
            }
          })

          if (!simMap.hasLayer(layer01)) {
            simMap.addLayer(layer01)
            simMap.addLayer(layerPolygon)
          }
        }
      },
      error: function (jqxhr) {
        // 后端服务无响应
        simMap.setView({
            lng: 107.9,
            lat: 35.84
          },
          4
        )

        var errMsg1 = '请求' + this.url + '时出错',
          errMsg2 = '服务器返回http状态码:' + jqxhr.status,
          errMsg3 =
          '服务器返回内容:' +
          (jqxhr.responseText ? jqxhr.responseText.trim() : '服务器未响应')

        console.log(errMsg1, errMsg2, errMsg3)
      }
    })
  }

  var LeafTool = (Object.freeze || Object)({
    handle2DLayer: handle2DLayer
  })

  function declare() {}

  function highlight() {}

  function reset() {}

  function addToggleButton(text, checked, onchange, toolbarID) {
    declare(onchange)
    var input = document.createElement('input')
    input.checked = checked
    input.type = 'checkbox'
    input.style.pointerEvents = 'none'
    var label = document.createElement('label')
    label.appendChild(input)
    label.appendChild(document.createTextNode(text))
    label.style.pointerEvents = 'none'
    var button = document.createElement('button')
    button.type = 'button'
    button.className = 'cesium-button'
    button.appendChild(label)

    button.onclick = function () {
      reset()
      highlight(onchange)
      input.checked = !input.checked
      onchange(input.checked)
    }

    document.getElementById(toolbarID || 'toolbar').appendChild(button)
  }

  function addToolbarButton(text, onclick, toolbarID) {
    declare(onclick)
    var button = document.createElement('button')
    button.type = 'button'
    button.className = 'cesium-button'
    button.onclick = function () {
      reset()
      highlight(onclick)
      onclick()
    }
    button.textContent = text
    document.getElementById(toolbarID || 'toolbar').appendChild(button)
  }

  function addDefaultToolbarButton(text, onclick, toolbarID) {
    addToolbarButton(text, onclick, toolbarID)
    defaultAction = onclick
  }

  function addDefaultToolbarMenu(options, toolbarID) {
    addToolbarMenu(options, toolbarID)
    defaultAction = options[0].onselect
  }

  function addToolbarMenu(options, toolbarID) {
    var menu = document.createElement('select')
    menu.className = 'cesium-button'
    menu.onchange = function () {
      reset()
      var item = options[menu.selectedIndex]
      if (item && typeof item.onselect === 'function') {
        item.onselect()
      }
    }
    document.getElementById(toolbarID || 'toolbar').appendChild(menu)

    if (!defaultAction && typeof options[0].onselect === 'function') {
      defaultAction = options[0].onselect
    }

    for (var i = 0, len = options.length; i < len; ++i) {
      var option = document.createElement('option')
      option.textContent = options[i].text
      option.value = options[i].value
      menu.appendChild(option)
    }
  }

  var Sandcastle = (Object.freeze || Object)({
    addToggleButton: addToggleButton,
    addToolbarButton: addToolbarButton,
    addToolbarMenu: addToolbarMenu,
    addDefaultToolbarButton: addDefaultToolbarButton,
    addDefaultToolbarMenu: addDefaultToolbarMenu
  })

  // @class Class
  // @aka N.Class

  // @section
  // @uninheritable

  // Thanks to John Resig and Dean Edwards for inspiration!

  function Class() {}

  Class.extend = function (props) {
    // @function extend(props: Object): Function
    // [Extends the current class](#class-inheritance) given the properties to be included.
    // Returns a Javascript function that is a class constructor (to be called with `new`).
    var NewClass = function () {
      // call the constructor
      if (this.initialize) {
        this.initialize.apply(this, arguments)
      }

      // call all constructor hooks
      this.callInitHooks()
    }

    var parentProto = (NewClass.__super__ = this.prototype)

    var proto = create(parentProto)
    proto.constructor = NewClass

    NewClass.prototype = proto

    // inherit parent's statics
    for (var i in this) {
      if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
        NewClass[i] = this[i]
      }
    }

    // mix static properties into the class
    if (props.statics) {
      extend(NewClass, props.statics)
      delete props.statics
    }

    // mix includes into the prototype
    if (props.includes) {
      checkDeprecatedMixinEvents(props.includes)
      extend.apply(null, [proto].concat(props.includes))
      delete props.includes
    }

    // merge options
    if (proto.options) {
      props.options = extend(create(proto.options), props.options)
    }

    // mix given properties into the prototype
    extend(proto, props)

    proto._initHooks = []

    // add method for calling all hooks
    proto.callInitHooks = function () {
      if (this._initHooksCalled) {
        return
      }

      if (parentProto.callInitHooks) {
        parentProto.callInitHooks.call(this)
      }

      this._initHooksCalled = true

      for (var i = 0, len = proto._initHooks.length; i < len; i++) {
        proto._initHooks[i].call(this)
      }
    }

    return NewClass
  }

  // @function include(properties: Object): this
  // [Includes a mixin](#class-includes) into the current class.
  Class.include = function (props) {
    extend(this.prototype, props)
    return this
  }

  // @function mergeOptions(options: Object): this
  // [Merges `options`](#class-options) into the defaults of the class.
  Class.mergeOptions = function (options) {
    extend(this.prototype.options, options)
    return this
  }

  // @function addInitHook(fn: Function): this
  // Adds a [constructor hook](#class-constructor-hooks) to the class.
  Class.addInitHook = function (fn) {
    // (Function) || (String, args...)
    var args = Array.prototype.slice.call(arguments, 1)

    var init =
      typeof fn === 'function' ?
      fn :
      function () {
        this[fn].apply(this, args)
      }

    this.prototype._initHooks = this.prototype._initHooks || []
    this.prototype._initHooks.push(init)
    return this
  }

  var GlobalUrl = Class.extend({
    initialize: function (url) {
      this.url = url
    }
  })

  function checkDeprecatedMixinEvents(includes) {
    if (!L || !L.Mixin) {
      return
    }

    includes = isArray(includes) ? includes : [includes]

    for (var i = 0; i < includes.length; i++) {
      if (includes[i] === L.Mixin.Events) {
        console.warn(
          'Deprecated include of L.Mixin.Events: ' +
          'this property will be removed in future releases, ' +
          'please inherit from L.Evented instead.',
          new Error().stack
        )
      }
    }
  }

  function getTile(vendor, type) {
    switch (vendor) {
      case 'tian':
        switch (type) {
          case 'normal':
            return L.layerGroup([L.tileLayer.chinaProvider('TianDiTu.Normal.Map', {
              maxZoom: 18,
              minZoom: 1
            }), L.tileLayer.chinaProvider('TianDiTu.Normal.Annotion', {
              maxZoom: 18,
              minZoom: 1
            })]);
          case 'satellite':
            return L.layerGroup([L.tileLayer.chinaProvider('TianDiTu.Satellite.Map', {
              maxZoom: 18,
              minZoom: 1
            }), L.tileLayer.chinaProvider('TianDiTu.Satellite.Annotion', {
              maxZoom: 18,
              minZoom: 1
            })]);
          case 'terrain':
            return L.layerGroup([L.tileLayer.chinaProvider('TianDiTu.Terrain.Map', {
              maxZoom: 18,
              minZoom: 1
            }), L.tileLayer.chinaProvider('TianDiTu.Terrain.Annotion', {
              maxZoom: 18,
              minZoom: 1
            })]);
        }
        break;
      default:
    }
  }

  function getTile(vendor, type) {
    switch (vendor) {
      case 'tian':
        switch (type) {
          case 'normal':
            return L.layerGroup([L.tileLayer.chinaProvider('TianDiTu.Normal.Map', {
              maxZoom: 18,
              minZoom: 1
            }), L.tileLayer.chinaProvider('TianDiTu.Normal.Annotion', {
              maxZoom: 18,
              minZoom: 1
            })]);
          case 'satellite':
            return L.layerGroup([L.tileLayer.chinaProvider('TianDiTu.Satellite.Map', {
              maxZoom: 18,
              minZoom: 1
            }), L.tileLayer.chinaProvider('TianDiTu.Satellite.Annotion', {
              maxZoom: 18,
              minZoom: 1
            })]);
          case 'terrain':
            return L.layerGroup([L.tileLayer.chinaProvider('TianDiTu.Terrain.Map', {
              maxZoom: 18,
              minZoom: 1
            }), L.tileLayer.chinaProvider('TianDiTu.Terrain.Annotion', {
              maxZoom: 18,
              minZoom: 1
            })]);
        }
        break;
      default:
    }
  }

  function Map(mapId, defaultLayer, crs) {
    'use strict';

    var crs = crs || L.CRS.EPSG3857,
      defaultLayer = defaultLayer || TianMap.getNormal();

    var map = L.map(mapId, { // 地图
      crs: crs, // web墨卡托
      // crs: L.CRS.EPSG4326,
      doubleClickZoom: false,
      layers: [defaultLayer],
      zoomControl: false,
      attributionControl: false
    });

    this.map = map;
    this.defaultLayer = defaultLayer;

    // // 设置工具
    // this.addZoom();
    // this.addMeasure();
    // this.addCoord();
    // this.addScale();

    return this;
  }

  Map.prototype = {
    getLmap: function () {
      return this.map;
    },
    setCView: function (center, zoom) {
      this.map.setView(center, zoom);

      return this;
    },
    setLayers: function (base, over, option) {
      var defaultLayer = this.defaultLayer;

      var base = base || { // 切换基础图层的对象
          "地图": defaultLayer,
          "影像": getTile('tian', 'satellite'),
          "地形": getTile('tian', 'terrain')
        },
        over = over || {},
        option = option || {
          position: 'topright',
          collapsed: false
        };

      var layerControl = L.control.layers(base, over, option).addTo(this.map);

      for (var p in over) {
        this.map.addLayer(over[p]);
      }

      return layerControl;
    },
    // addMeasure: function() {
    //     // 测距离和面积
    //     L.control.measure({
    //         position: 'topleft',
    //         primaryLengthUnit: 'meters',
    //         primaryAreaUnit: 'sqmeters',
    //         // activeColor: '#ABE67E',
    //         activeColor: '#ff0000',
    //         // completedColor: '#C8F2BE',
    //         completedColor: '#ff0000',
    //         localization: 'cn',
    //         popupOptions: { className: 'leaflet-measure-resultpopup', autoPanPadding: [10, 10] }
    //     }).addTo(this.map);

    //     return this;
    // },
    addZoom: function () {
      // 缩放
      L.control.zoom({
        zoomInTitle: '放大',
        zoomOutTitle: '缩小'
      }).addTo(this.map);

      return this;
    },
    addCoord: function () {
      // 经纬度坐标
      L.control.coordinates({
        position: "bottomleft",
        labelTemplateLat: "纬度: {y}",
        labelTemplateLng: "经度: {x}",
        enableUserInput: false
      }).addTo(this.map);

      return this;
    },
    addScale: function () {
      L.control.scale({
        imperial: false
      }).addTo(this.map);

      return this;
    },
    // addReturn: function() {
    //     var map = this.map;

    //     // 地图置中
    //     L.easyButton('<span class="fa fa-undo"></span>', function(e) {
    //         map.setView(e._map._lastCenter, 12);
    //     }, '返回').addTo(map);

    //     return this;
    // },
    // addSlider: function(callee) {
    //     L.control.slider(callee, {
    //         position: 'topleft',
    //         max: 1,
    //         value: 1,
    //         step: 0.1,
    //         size: '100px',
    //         // orientation: 'vertical',
    //         orientation: 'horizontal',
    //         id: 'slider',
    //         title: '透明度',
    //         logo: 'S'
    //     }).addTo(this.map);

    //     return this;
    // }
  }

  var createMap = function (id, defaultLayer, crs) {
    return new Map(id, defaultLayer, crs);
  }

  // misc
  var oldN = window.N

  function noConflict() {
    window.N = oldN

    return this
  }

  window.N = exports

  exports.version = version
  exports.noConflict = noConflict
  exports.Util = Util
  exports.Data = Data
  exports.Gain = Gain
  exports.Formatter = Formatter
  exports.Color = Color
  exports.Dom = Dom
  exports.Class = Class
  exports.CesiumTool = CesiumTool
  exports.LeafTool = LeafTool
  exports.GlobalUrl = GlobalUrl
  exports.ready = ready
  exports.Constant = Constant
  exports.Sandcastle = Sandcastle
  exports.getTile = getTile
  exports.Map = Map
  exports.map = createMap
})